﻿
module internal Octopus.VMDeployer.Utility

open System.Management


//
//--------------- WMI Stuff
//

type ReturnCode =
| Completed = 0
| Started = 4096
| Failed = 32768
| AccessDenied = 32769
| NotSupported = 32770
| Unknown = 32771
| Timeout = 32772
| InvalidParameter = 32773
| SystemInUse = 32774
| InvalidState = 32775
| IncorrectDataType = 32776
| SystemNotAvailable = 32777
| OutofMemory = 32778


type JobState =
| New = 2
| Starting = 3
| Running = 4
| Suspened = 5
| ShuttingDown = 6
| Completed = 7
| Terminated = 8
| Killed = 9
| Exception = 10
| Service = 11



let get_WmiObjects (query:string) =
  let scope = new ManagementScope(@"root\cimv2", null)
  let objQuery = new ObjectQuery(query)
  use searcher = new ManagementObjectSearcher(scope, objQuery)
  try
    let coll = searcher.Get()
    let l = new System.Collections.Generic.List<ManagementObject>()
    for el in coll do l.Add(el :?> ManagementObject)
    l.ToArray()
  with _ -> [||]


let get_WmiObject query =
  let objs = get_WmiObjects query
  if objs.Length = 0 then null
  else objs.[0]


let get_WmiObjects2 (scope:ManagementScope) (query:string) =
  let objQuery = new ObjectQuery(query)
  use searcher = new ManagementObjectSearcher(scope, objQuery)
  try
    let coll = searcher.Get()
    let l = new System.Collections.Generic.List<ManagementObject>()
    for el in coll do l.Add(el :?> ManagementObject)
    l.ToArray()
  with _ -> [||]


let get_WmiObject2 scope query =
  let objs = get_WmiObjects2 scope query
  if objs.Length = 0 then null
  else objs.[0]


let getServiceObject (scope:ManagementScope) serviceName =
  scope.Connect()
  let wmiPath = new ManagementPath(serviceName)
  use serviceClass = new ManagementClass(scope, wmiPath, null)
  use services = serviceClass.GetInstances()  
  
  let en = services.GetEnumerator()
  if en.MoveNext() then en.Current :?> ManagementObject
  else failwith "Service object not found"


let jobCompleted (outParams:ManagementBaseObject) scope =
  let jobPath = outParams.["Job"] :?> string
  use job = new ManagementObject(scope, new ManagementPath(jobPath), null)
  //Try to get storage job information
  job.Get()
  while (job.["JobState"] :?> uint16) = uint16(JobState.Starting) || (job.["JobState"] :?> uint16) = uint16(JobState.Running) do
    //printfn "In progress... %s%% completed." (job.["PercentComplete"])
    System.Threading.Thread.Sleep(1000)
    job.Get()
  //Figure out if job failed
  let jobState = job.["JobState"] :?> uint16
  if jobState <> uint16(JobState.Completed) then
    let jobErrorCode = job.["ErrorCode"] :?> uint16
    failwithf "Error Code: %d, ErrorDescription: %s" jobErrorCode (job.["ErrorDescription"].ToString())



// Minimum supported client Windows 2000 Professional 
// Minimum supported server Windows 2000 Server 
let renewDCHPAddresses () =
  let query = "select * from Win32_NetworkAdapterConfiguration where DHCPEnabled='True'"
  for nic in get_WmiObjects query do
    nic.InvokeMethod("RenewDHCPLease", null) |> ignore


//
//--------------- Octopus server - remote function invocation
//

let tryExecute_remote_Octopus_function (octopusServer:string array) (functionInfo:string array) =
  let getClient() =
      let rec findTheServer addresses =
        match addresses with
        | [] -> failwith "Octopus server not found. All the addresses don't respond."
        | h::t -> try new System.Net.Sockets.TcpClient(h, 17400)
                  with _ -> findTheServer t
      findTheServer (Array.toList octopusServer)
  
  // Sometimes it happens that the NICs don't get the dynamic address on windows start,
  // so, if it fails to contact the octopus service, I try to renew the ip addresses
  let client =
    try getClient()
    with _ -> renewDCHPAddresses(); getClient()
  
  try
    use stream = client.GetStream()
    use sw = new System.IO.StreamWriter(stream)
    use sr = new System.IO.StreamReader(stream)
    functionInfo |> Array.iter (fun x -> sw.WriteLine(x)) 
    sw.Flush()
    let result = System.Boolean.Parse(sr.ReadLine())
    if not(result) then failwith (sr.ReadLine())
    else ()
  finally
    client.Close()


let execute_remote_Octopus_function octopusServer (functionInfo:string array) =
  let client =
    try new System.Net.Sockets.TcpClient(octopusServer, 17400)
    with _ -> failwith "Connection refused"
  try
    use stream = client.GetStream()
    use sw = new System.IO.StreamWriter(stream)
    use sr = new System.IO.StreamReader(stream)
    functionInfo |> Array.iter (fun x -> sw.WriteLine(x)) 
    sw.Flush()
    let result = System.Boolean.Parse(sr.ReadLine())
    if not(result) then failwith (sr.ReadLine())
    else ()
  finally
    client.Close()



//
//----------------------- Other stuff
//

let trySomeTimes (times,delay:int) (f:unit -> unit) =
  let delays = Array.create (times-1) delay
  let mutable success = false
  for d in delays do
    if not success then
      try f(); success <- true
      with _ -> System.Threading.Thread.Sleep(d)
  if not success then f()   // last try with raise if it fails

